#!/usr/bin/python2.4
#
# TestPlan includes:
#   test plan name
#   testers(job_owner) and their emails
#   machines
#   tests
#   start time
#   finish time


import getpass
import itertools
import os

import constants
import control_file
from logger import logger
import machine_manager
import utils


class TestPlan(object):

  def __init__(self, name, tests, machines,
               job_owners=None, start_time=None, finish_time=None,
               check_config=True, **create_job_args):
    self.name = name
    self.tests = tests
    self.machines = machines
    self.machine_manager = machine_manager.MachineManager(**machines)
    if job_owners is None:
      job_owners = (getpass.getuser(),)
    self.job_owners = job_owners
    self.start_time = start_time
    self.finish_time = finish_time
    self.check_config = check_config
    create_job_args.setdefault('email_list', ', '.join(self.job_owners))
    self.create_job_args = create_job_args

    self.job_name_to_test = dict((test.job_name, test)
                                 for test in self.tests)
    self.hosts = None
    # Make sure no two tests have the same job name
    if len(tests) != len(self.job_name_to_test):
      raise AssertionError('test job_name must be unique')

  def __repr__(self):
    results = '%s(name=%r, %d tests' % (type(self).__name__,
                                        self.name,
                                        len(self.tests))
    if self.hosts is not None:
      results += ', %d hosts' % len(self.hosts)
    results += ')'
    return results

  def __getattr__(self, name):
    try:
      return self.create_job_args[name]
    except KeyError:
      pass

    try:
      return getattr(self.machine_manager, name)
    except AttributeError:
      raise AttributeError('%r object has no attribute %r' % (
                           type(self).__name__, name))

  def setup(self, query_hosts=True):
    for test in self.tests:
      test.setup()
    self.machine_manager.setup()
    if query_hosts:
      self.get_hosts(update_hosts=True)

  def get_test_views_filter_data(self, **overrides):
    filter_data = {
      'job_name__startswith': '%s: ' % self.name,
      'exclude_labels': [constants.TL_INVALIDATED],
      'sort_by': ['job_idx', 'test_idx']
    }
    if self.job_owners:
      filter_data['job_owner__in'] = self.job_owners
    if self.start_time:
      filter_data['job_queued_time__gte'] = self.start_time
    if self.finish_time:
      filter_data['job_queued_time__lte'] = self.finish_time
    utils.update_dict(filter_data, overrides)
    return filter_data

  def get_sql_query_conditions(self, *additions):
    conditions = ["job_name LIKE '%s: %%'" % self.name]
    if len(self.job_owners) == 1:
      conditions.append("job_owner = '%s'" % self.job_owners[0])
    elif len(self.job_owners) > 1:
      conditions.append("job_owner IN (%s)" %
                        utils.quoted_array(self.job_owners))
    if self.start_time:
      conditions.append("job_queued_time >= '%s'" % self.start_time)
    if self.finish_time:
      conditions.append("job_queued_time <= '%s'" % self.finish_time)

    conditions += additions
    return ' AND '.join(conditions)

  def get_hosts(self, op=constants.DEFAULT_GET_HOSTS_METHOD,
                update_hosts=False, **filter_data):

    def hosts_differ(hosts1, hosts2):
      if len(hosts1) != len(hosts2):
        return True
      for host1, host2 in itertools.imap(None, hosts1, hosts2):
        if host1.hostname != host2.hostname:
          return True
      return False

    hosts = getattr(self.machine_manager, op)(**filter_data)
    if update_hosts:
      if self.hosts and hosts_differ(self.hosts, hosts):
        logger.error('Hosts have changed, number from %s to %s' %
                     (len(self.hosts), len(hosts)))
      self.hosts = hosts
    return hosts

  @property
  def hostnames(self):
    return (host.hostname for host in self.hosts)

  def get_test(self, job_name):
    return self.job_name_to_test[job_name]

  def get_job_full_name(self, test, *infix):
    parts = [self.name, 'Dioxide']
    parts += infix
    parts.append(test.job_name)
    return ': '.join(parts)

  def create_job(self, job_name, hosts, job_name_infix=(), **overrides):
    test = self.get_test(job_name)
    if job_name_infix is None:
      job_name_infix = ()
    elif isinstance(job_name_infix, basestring):
      job_name_infix = (job_name_infix,)
    create_job_args = {
      'name': self.get_job_full_name(test, *job_name_infix),
      'priority': 'Medium',
      'run_verify': True,
      'reboot_before': 'Never',
      'reboot_after': 'Never',
      'parse_failed_repair': True,
      # Managed by test:
      #  control_file, control_type, synch_count, timeout, max_runtime_hrs,
      #  dependencies
      # Managed by test_plan:
      #  email_list, name
      # Not used:
      #  meta_hosts, one_time_hosts, atomic_group_name, is_template,
    }
    create_job_args.update(self.create_job_args, hosts=hosts)
    return test.create_job(create_job_args, **overrides)

  def pformat(self, include_tests=False, include_hosts=False,
              include_test_control=False):
    headers = ['%s:%r' % (attr, getattr(self, attr))
               for attr in ('name', 'job_owners', 'start_time', 'finish_time',
                            'check_config')]
    results = [
        ' '.join(headers),
        'create_job_args: %r' % self.create_job_args,
        'machines: %r' % self.machines
    ]
    if include_tests:
      results.extend('tests[%d] %s' % (i, test.pformat(include_test_control))
                     for i, test in enumerate(self.tests))
    if include_hosts:
      results.append('hosts: %s' % utils.pformat_array(list(self.hostnames)))
    return '\n'.join(results)


def read_test_plans_from_file(filename):
  test_plans = []
  test_plan_dir = os.path.dirname(filename)

  def create_test(path, *args, **dargs):
    if control_file.get_autotest_test_name(path) is None:
      path = os.path.abspath(os.path.join(test_plan_dir, path))
    return control_file.ControlFile(path=path, *args, **dargs)

  def create_test_plan(*args, **dargs):
    test_plan = TestPlan(*args, **dargs)
    test_plans.append(test_plan)
    return test_plan

  execfile(filename, {'create_test': create_test,
                      'create_test_plan': create_test_plan})

  return test_plans
